package com.letao.server.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.letao.server.dto.*;
import com.letao.server.entity.UserInfo;
import com.letao.server.mapper.UserInfoMapper;
import com.letao.server.service.IUserService;
import com.letao.server.utils.RSAUtils;
import com.letao.server.utils.RegexUtils;
import com.letao.server.utils.UserHolderUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.letao.server.constants.RedisConstants.*;
import static com.letao.server.constants.SystemConstants.USER_NICK_NAME_PREFIX;

/**
 * 用户模块功能处理接口实现类
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements IUserService {

    @Resource
    private UserInfoMapper userInfoMapper;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Resource
    private JavaMailSender javaMailSender;

    @Value("${spring.mail.username}")
    private String form;

    /**
     * 用于检验信息是否正确以及注册信息
     *
     * @param userInfo 注册用于初始化的用户信息
     * @return Result 返回结果
     */
    @Override
    public Result register(UserInfo userInfo) {
        // 验证手机号格式是否正确
        if (RegexUtils.isPhoneInvalid(userInfo.getPhoneNumber())) {
            // 如果不正确返回错误信息
            return Result.fail("手机号格式错误");
        }
        // 检测是否重复注册
        UserInfo phoneNumber = query().eq("phone_number", userInfo.getPhoneNumber()).one();
        if (phoneNumber != null) {
            return Result.fail("手机号已注册");
        }
        // 验证邮箱格式是否正确
        if (userInfo.getEmail() != null && RegexUtils.isEmailInvalid(userInfo.getEmail())) {
            // 如果不正确返回错误信息
            return Result.fail("邮箱格式错误");
        }

        // 检验密码格式是否正确
        String code = userInfo.getPassword();
        try {
            // 解密密码
            code = RSAUtils.decrypt(code);
        } catch (Exception e) {
            return Result.fail("密码加密错误");
        }
        if (RegexUtils.isCodeInvalid(code)) {
            return Result.fail("密码格式错误");
        }
        log.debug("手机号为 {} 的用户注册的密码为 {}", userInfo.getPhoneNumber(), code);
        // 对密码进行加密
        code = BCrypt.hashpw(code);
        log.info("加密后字符串的长度为" + code.length());
        userInfo.setPassword(code);
        // 验证是否可以进行插入操作
        try {
            save(userInfo);
        } catch (DuplicateKeyException e) {
            // 如果不可以说明信息重复,返回错误信息
            return Result.fail("注册错误");
        }
        // 插入成功,记录日志并回复成功结果
        log.info("成功创建ID = {}的用户", userInfo.getId());
        return Result.success();
    }

    /**
     * 用于校验数据信息以及更新用户信息
     *
     * @param userInfo 更新的用户信息
     * @return Result 返回结果
     */
    @Override
    public Result updateUser(UserInfo userInfo) {
        // 检测是否更新邮箱
        if (userInfo.getEmail() != null)
            if (RegexUtils.isEmailInvalid(userInfo.getEmail())) {
                // 如果邮箱格式错误报错
                return Result.fail("邮箱格式不正确");
            }
        // 更新数据
        UserInfoDTO user = UserHolderUtils.getUser();
        log.info("11111111111111111111111111ID = {}的用户更新了自己的数据:{}", user.getId(), userInfo);
        userInfo.setId(user.getId());
        updateById(userInfo);

        return Result.success();
    }

    /**
     * 用于处理用户的登录
     *
     * @param loginForm 用户的登录信息
     * @return Result 返回结果
     */
    @Override
    public Result login(UserLoginFormDTO loginForm, HttpServletRequest request) {
        log.debug(loginForm.toString());
        // 查询是否存在用户
        UserInfo user = query().eq("phone_number", loginForm.getPhoneNumber()).one();
        if (user == null) {
            // 若不存在则返回错误信息
            return Result.fail("用户不存在");
        }
        if (user.getPassword() == null) {
            // 若没有设置密码则返回错误
            return Result.fail("密码错误");
        }
        // 判断密码是否正确
        String code = loginForm.getPassword();
        // 解密密码
        try {
            code = RSAUtils.decrypt(code);
        } catch (Exception e) {
            return Result.fail("密码错误");
        }
        String codeStored = user.getPassword();
        if (!BCrypt.checkpw(code, codeStored)) {
            // 若密码错误则返回错误信息
            return Result.fail("密码错误");
        }
        // 生成token
        String token = UUID.randomUUID().toString(true);
        UserInfoDTO userDTO = BeanUtil.copyProperties(user, UserInfoDTO.class);
        // 把用户数据格式化成map
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName, fieldValue) -> {
                            if (fieldValue == null) return null;
                            return fieldValue.toString();
                        }));
        String tokenKey = LOGIN_USER_KEY + token;
        // 把用户数据存储到Redis里
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.DAYS);
        // 返回token值
        log.info("ID = {}的用户进行登录", userDTO.getId());
        HttpSession session = request.getSession();
        session.setAttribute("authorization", token);
        return Result.success(token);
    }

    /**
     * 用于校验用户信息以及更新用户登录密码
     *
     * @param password 新旧密码(RSA加密)
     * @return Result 返回结果
     */
    @Override
    public Result changePassword(PasswordChangeDTO password) {
        log.info("{}", password);
        String passwordOld = password.getPasswordOld();
        String passwordNew = password.getPasswordNew();
        // 解密密码
        try {
            passwordOld = (passwordOld == null || "".equals(passwordOld)) ? null : RSAUtils.decrypt(passwordOld);
            passwordNew = RSAUtils.decrypt(passwordNew);
        } catch (Exception e) {
            // 解密失败说明密码加密错误
            return Result.fail("请求格式错误");
        }
        log.info("new{},old{}", passwordNew.toString(), passwordOld.toString());
        // 检测新旧密码是否相同密码
        if (passwordOld != null && passwordOld.equals(passwordNew))
            return Result.fail("新旧密码不能相同");
        // 获取用户信息
        UserInfoDTO user = UserHolderUtils.getUser();
        UserInfo userInfo = getById(user.getId());
        String codeStored = userInfo.getPassword();
        if (codeStored != null)
            if (!BCrypt.checkpw(passwordOld, codeStored)) {
                // 若密码错误则返回错误信息
                return Result.fail("密码错误");
            }
        // 对新密码进行加密
        String code = BCrypt.hashpw(passwordNew);
        userInfo.setPassword(code);
        // 更新数据
        updateById(userInfo);
        log.info("ID = {}的用户更新了密码", user.getId());
        return Result.success();
    }

    /**
     * 用于批量获取指定范围的用户信息
     *
     * @param name     用户名模糊搜索
     * @param sex      用户性别
     * @param begin    创建时间段起始时间
     * @param end      创建时间段结束时间
     * @param page     请求页数
     * @param pageSize 页大小
     * @return Result 返回结果
     */
    @Override
    public Result getPageInfo(String name, Integer sex, String begin, String end, Integer page, Integer pageSize) {
        IPage<UserInfo> userPage = new Page<>(page, pageSize);
        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        // 如果需要查询名字
        if (name != null) wrapper.like("account", name);
        // 如果需要指定性别
        if (sex != null) if (sex == 0 || sex == 1) wrapper.eq("sex", sex);
        // 如果要指定创建时间段
        if (begin != null && end != null) {
            if (RegexUtils.isDateFromInvalid(begin) && RegexUtils.isDateFromInvalid(end)) {
                wrapper.between("create_time", begin, end);
            }
        }
        IPage<UserInfo> userInfoIPage = userInfoMapper.selectPage(userPage, wrapper);
        // 设置返回对象
        PageDTO<UserInfo> userInfoPageDTO = new PageDTO<>();
        userInfoPageDTO.setRows(userInfoIPage.getRecords());
        userInfoPageDTO.setTotalSize(userInfoIPage.getTotal());
        userInfoPageDTO.setPages(userInfoIPage.getPages());
        userInfoPageDTO.setCurrentPage(userInfoIPage.getCurrent());
        userInfoPageDTO.setPageSize(userInfoIPage.getSize());
        log.info("管理员批量查询了用户信息");
        return Result.success(userInfoPageDTO);
    }

    /**
     * 用于获取手机验证码
     *
     * @param phoneNumber 手机号
     * @return Result 返回结果
     */
    @Override
    public Result sendCode(String phoneNumber) {
        // 检验手机号
        if (RegexUtils.isPhoneInvalid(phoneNumber)) {
            // 不符合返回错误信息
            return Result.fail("手机号格式错误");
        }
        String key = LOGIN_CODE_KEY + phoneNumber;
        Long expire = stringRedisTemplate.getExpire(key, TimeUnit.SECONDS);
        // 若上次请求间隔过短,则返回错误信息
        if (expire >= (LOGIN_CODE_TTL_LEFT) * 60) {
            return Result.fail("请求过于频繁,请稍后重试");
        }
        // 生成六位数字验证码
        String code = RandomUtil.randomNumbers(6);
        // 保存验证码到Redis
        stringRedisTemplate.opsForValue().set(key, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
        // 发送验证码
        log.info("手机号为 {} 的访客请求了验证码, 验证码为 {} ", phoneNumber, code);
        SimpleMailMessage mailMessage = new SimpleMailMessage();
        mailMessage.setFrom(form);
        mailMessage.setTo("1292816293@qq.com");
        mailMessage.setSubject("乐淘电子商务系统登录验证码");
        mailMessage.setText(code);
        javaMailSender.send(mailMessage);
        return Result.success();
    }

    /**
     * 用于手机验证码登录/注册服务
     *
     * @param loginForm 验证信息
     * @return Result 返回结果
     */
    @Override
    public Result mlogin(UserLoginFormDTO loginForm) {
        // 检验手机号格式是否正确
        String phoneNumber = loginForm.getPhoneNumber();
        if (RegexUtils.isPhoneInvalid(phoneNumber)) {
            return Result.fail("手机号格式错误");
        }
        // 从Redis里获取保存的验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phoneNumber);
        String code = loginForm.getCode();
        // 比较是否一致
        if (cacheCode == null || !cacheCode.equals(code)) {
            // 不一致，报错
            return Result.fail("验证码错误");
        }
        // 删除验证码
        stringRedisTemplate.delete(LOGIN_CODE_KEY + phoneNumber);
        // 根据手机号查询用户信息
        UserInfo userInfo = query().eq("phone_number", phoneNumber).one();
        // 判读用户是否存在
        if (userInfo == null) {
            userInfo = createUserWithPhone(phoneNumber);
            log.info("成功创建ID = {}的用户", userInfo.getId());
        }
        // 保存用户信息到Redix
        String token = UUID.randomUUID().toString(true);
        UserInfoDTO userDTO = BeanUtil.copyProperties(userInfo, UserInfoDTO.class);
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create()
                        .setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName, fieldValue) -> {
                            if (fieldValue == null) return null;
                            return fieldValue.toString();
                        }));
        String tokenKey = LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
        stringRedisTemplate.expire(tokenKey, LOGIN_USER_TTL, TimeUnit.DAYS);
        log.info("ID = {}的用户进行登录", userDTO.getId());
        //返回token
        return Result.success(token);
    }

    /**
     * 通过手机号注册用户
     *
     * @param phoneNumber 需要注册的手机号
     * @return UserInfo 生成的用户信息
     */
    private UserInfo createUserWithPhone(String phoneNumber) {
        UserInfo userInfo = new UserInfo();
        // 设置用户基本信息
        userInfo.setPhoneNumber(phoneNumber);
        // 随机生成用户名
        userInfo.setAccount(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));
        // 保存用户
        save(userInfo);
        return userInfo;
    }

}
